package org.bhf.jsp;

/**
 * Copyright (c) 2007, Blue Hole Software. All rights reserved.
 * Code licensed under the Apache 2.0 License:
 * http://www.apache.org/licenses/
 */

import com.google.inject.Injector;
import org.bhf.facilities.ProjectInjectors;
import org.bhf.json.JSONWriter;
import org.bhf.service.CSRTokenFacility;
import org.bhf.service.Component;
import org.bhf.service.ComponentInfo;
import org.bhf.service.Service;
import org.bhf.service.ServiceGroup;
import org.bhf.service.ServiceGroupInfo;
import org.bhf.service.ServiceMethod;
import org.bhf.service.URLRouter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Method;

/**
 * Create a JavaScript Component object with methods.
 * @exclude
 */
public class ComponentTag extends TagSupport
{
    private static final long serialVersionUID = -1676505332940116162L;

    /** true if we use an absolute URL to the Component */
    protected boolean       absolute;

    /** Reference to the Component object, must not be null */
    protected Component     component;

    /** Current user role */
    protected String        role;

    //
    // Properties
    //

    /**
     * Sets the Component object.
     *
     * @param component The Component object.
     */
    public void setComponent( final Component component )
    {
        this.component = component;
    }

    /**
     * <code>true</code> if the URL to the Component should be absolute; <code>false</code> if relative to the HTML base.
     *
     * @param absolute <code>true</code> if the URL to the Component should be absolute;
     *      <code>false</code> if relative to the HTML base.
     */
    public void setAbsolute( final boolean absolute )
    {
        this.absolute = absolute;
    }

    /**
     * Set the current role name.
     *
     * @param role Role name. Only method allowed for this role will be generated in the JavaScript stub.
     */
    public void setRole( final String role )
    {
        this.role = role;
    }

    /**
     * Render the component declaration and initialization.
     *
     * @throws JspException If a JSP exception has occurred.
     *
     * @return <code>Tag.EVAL_BODY_INCLUDE</code>.
     */
    @Override
    public int doStartTag()
        throws JspException
    {
        try
        {
            final Injector                      injector        = ProjectInjectors.getProjectInjector();
            final Writer                        writer          = pageContext.getOut();
            final Class<? extends Component>    sClazz          = component.getClass();
            final String                        componentName   = Service.Util.getName( sClazz );
            final ServiceGroup                  serviceGroup    = ServiceGroup.getServiceGroupForService( sClazz );
            final CSRTokenFacility              tokens          = injector.getInstance( CSRTokenFacility.class );
            final URLRouter                     router          = injector.getInstance( URLRouter.class );
                  String                        route           = router.reverseRoute( sClazz,  "{method}", null, component.getId() );

            if( componentName == null )
                throw new JspException( "Component class " + sClazz.getName() + " does not have a ServiceName annotation" );
            if( serviceGroup == null )
                throw new JspException( "The ServiceGroup for " + sClazz.getName() + " has not yet been loaded." );

            final String moduleName = ServiceGroupInfo.Util.getName( serviceGroup.getClass() );
            if( moduleName == null )
                throw new JspException( "ServiceGroup class " + serviceGroup.getClass().getName() + " does not have a ServiceGroupInfo annotation" );

            // Routes should be relative at this point
            if( route.startsWith( "/" ) )
            {
                route = route.substring( 1 );
            }

            // Instantiate
            writer.write( "function (){var that=new BHF.Component('" );
            if( absolute )
            {
                final HttpServletRequest    request     = (HttpServletRequest) pageContext.getRequest();
                final StringBuffer          baseRef     = TagUtils.createServerUriStringBuffer(
                                                            request.getScheme(),
                                                            request.getServerName(),
                                                            request.getServerPort(),
                                                            request.getContextPath()
                                                        );
                writer.write( baseRef.toString() );
                writer.write( '/' );
            }
            writer.write( route );
            writer.write( "'," ); new JSONWriter().writeObject( component, writer );
            writer.write( ");" );
            
            // Use server side state?
            writer.write( "that.setUseServerCache(" ); writer.write( Boolean.toString( ComponentInfo.Util.isCachedServerSide( sClazz ) ) ); writer.write( ");" );

            // Add token
            {
                final String token = tokens.getToken( ServiceGroupInfo.Util.getName( serviceGroup.getClass() ), componentName );
                writer.write( "that.setToken('" );
                writer.write( token );
                writer.write( "');" );
            }
            
            // Add methods
            for( final Method m : sClazz.getMethods() )
            {
                if( ServiceMethod.Util.isServiceMethod( m ) )
                {
                    if( role == null || ServiceMethod.Util.hasAccess( m, role ) )
                    {
                        writer.write( "that.addMethod('" );
                        writer.write( m.getName() );
                        writer.write( "');" );
                    }
                }
            }

            // Add events
            {
                final Class<?> eventGroup = ComponentInfo.Util.getEventGroup( sClazz );

                writer.write( "that.events={};" );
                if( eventGroup != null )
                {
                    for( final Method event : eventGroup.getMethods() )
                    {
                        writer.write( "that.events." ); writer.write( event.getName() );
                        writer.write( "=new BHF.util.CustomEvent('" ); writer.write( event.getName() ); writer.write( "');" );
                    }
                }
            }

            writer.write( "return that;}()" );
        }
        catch( IOException e )
        {
            throw new JspException( e );
        }

        return Tag.EVAL_BODY_INCLUDE;
    }

    @Override
    public void release()
    {
        absolute    = false;
        component   = null;
        role        = null;
    }
}